#include "core/core_utils.h"
#include "VirtualFileSystem.h"
#include <iostream>
#include "physfs-2.0.2/physfs.h"
#include <sstream>
#include <boost/filesystem.hpp>

namespace lpe{
	namespace resman{
		CVirtualFileSystem::CVirtualFileSystem(void)
			:m_systemResourcesBasePath("foo")
			,m_isInitialized(false)
			,m_isReadOnly(true)
			,m_deployMode(DPMOD_NORMAL)
		{

		}
		
		void CVirtualFileSystem::setDeployPolicy(DEPLOY_MODE mode)
		{
			m_deployMode = mode;
		}
		CVirtualFileSystem::DEPLOY_MODE CVirtualFileSystem::getDeployPolicy() const
		{
			return m_deployMode;
		}
		bool CVirtualFileSystem::pathCreator( boost::filesystem::path path_to_create )
		{
			if (!boost::filesystem::exists(path_to_create))
			{
				pathCreator(path_to_create.parent_path());
			}
			try{
				//if not create the directory
				boost::filesystem::create_directory(path_to_create);
				return true;
			}catch(boost::filesystem::filesystem_error& fe)
			{
				std::stringstream ss;
				ss << "Error creating path " << path_to_create.string() << "\n";
				THROW_EXCEPTION(lpe::core::incorrectPathException,ss.str());
			}
			return true;
		}

		CVirtualFileSystem::~CVirtualFileSystem(void)
		{
			reset();
		}
		int CVirtualFileSystem::init(const char* basePath, bool create)
		{
			assertion(!m_isInitialized, "System's already initialized. Make a call to reset before to call init again");
			PHYSFS_init(NULL);
			// setting current path as an starting directories
			try{
				setSystemResourcesBasePath(basePath, create);
			}
			catch(lpe::core::fileManagementException& fme)
			{
				std::stringstream ss;
				ss << AT <<": Exception initiating VFS when setting the System resource Path. Reason: " << fme.what();
				THROW_EXCEPTION(lpe::core::fileManagementException,ss.str());
				throw;
			}
			m_isInitialized = true;
			return 0;
		}

		void CVirtualFileSystem::setSystemResourcesBasePath(const char* path, bool create)
		{
			try{
				boost::filesystem::path basePath(path);
				basePath = boost::filesystem::system_complete(basePath);
				if(create)
				{
					pathCreator(basePath);
				}
				if(!exists(basePath)) // does exists basepath?
				{
					std::stringstream ss;
					ss << "Path " << basePath << " doesn't exists\n";
					THROW_EXCEPTION(lpe::core::incorrectPathException,ss.str());
				}
				// all is correct?
				m_systemResourcesBasePath = boost::filesystem::system_complete(basePath);
			}
			catch(const boost::filesystem::filesystem_error& fe)
			{
				THROW_EXCEPTION(lpe::core::fileManagementException,fe.what());
			}
		}

		bool CVirtualFileSystem::createSubPath(const char* subPath)
		{
			if(!m_isInitialized)
			{
				std::stringstream ss;
				ss << "Cannot create " << subPath << " .You must initialize class CVirtualFileSystem before to create a subpath";
				THROW_EXCEPTION(lpe::core::uninitializedComponentException,ss.str());
			}
			boost::filesystem::path p(m_systemResourcesBasePath);
			p /= boost::filesystem::path(subPath);
			pathCreator(p);
			return true;

		}
		int CVirtualFileSystem::addLocationToSearch(const char* location, unsigned int order)
		{
			boost::filesystem::path locPath(m_systemResourcesBasePath);
			boost::filesystem::path subPath(location);
			locPath = locPath / subPath;
			if(!boost::filesystem::exists(locPath))
			{
				std::stringstream ss;
				ss << "The location to search  " << locPath << " doesn't exists. You can create the path by using createSubPath, but CVirtualFileSystem does not create a folder by itself.";
				THROW_EXCEPTION(lpe::core::incorrectPathException,ss.str());
			}
			PHYSFS_addToSearchPath(locPath.string().c_str(), order);

			return 0;
		}


		std::string CVirtualFileSystem::getWriteDirectory() const
		{
			return m_writingAllowedPath.string();
		}

		int CVirtualFileSystem::setWriteDirectory( const char* location )
		{
			assertion(m_isInitialized,"Virtual file system is not initilized yet");
			boost::filesystem::path writePath(location);
			
			writePath = m_systemResourcesBasePath / writePath;
			if(!boost::filesystem::exists(writePath))
			{
				std::stringstream ss;
				ss << "The path " << writePath << " doesn't exists. You can create the path by using createSubPath";
				THROW_EXCEPTION(lpe::core::incorrectPathException,ss.str());
			}
			pathCreator(writePath);
			m_writingAllowedPath = writePath;
			PHYSFS_setWriteDir(m_writingAllowedPath.string().c_str());
			
			return 0;
		}

		bool CVirtualFileSystem::checkIfFileExists( const char* fileName )
		{
			if(PHYSFS_exists(fileName)== 0)
				return false;
			return true;
		}
		void CVirtualFileSystem::deleteDeployedFiles()
		{
			const std::vector<std::string> v = m_deployedManager.getKeys();
			for(unsigned int i = 0; i < v.size(); i++)
				deleteFile(v[i].c_str());
		}
		void CVirtualFileSystem::deleteFile(const char* fileName)
		{
			PHYSFS_delete(m_deployedManager.getValue(std::string(fileName)).c_str());
			m_deployedManager.removeItem(fileName);
			assert(!m_deployedManager.exists(fileName));
		}

		void CVirtualFileSystem::reset()
		{
			if(m_isInitialized)
				PHYSFS_deinit();
			m_isInitialized = false;
		}

		std::string CVirtualFileSystem::deployFile(const char* fileName)
		{
			return deployFile(fileName, m_deployMode);
		}
		std::string CVirtualFileSystem::deployFile( const char* fileName, DEPLOY_MODE mode )
		{
			if(!checkIfFileExists(fileName))
			{
				std::stringstream ss;
				ss << "File " << fileName << " could not be found!!!";
				THROW_EXCEPTION(lpe::core::fileNotFoundException,ss.str());
			}
			if (m_deployedManager.exists(fileName))
			{
				return m_deployedManager.getValue(fileName);
			}
			//first open file and put into memory
			PHYSFS_File* fileSource = PHYSFS_openRead(fileName);
			unsigned int fileSize = static_cast<unsigned int>(PHYSFS_fileLength(fileSource));
			char* buffer = new char[fileSize];
			uint64_t bytesReaded = PHYSFS_read(fileSource,buffer,1,fileSize);
			assert(bytesReaded == fileSize);
			PHYSFS_close(fileSource);


			// depending on the user decision, generate a name of the file
			char* generatedName;
			if(mode == CVirtualFileSystem::DPMOD_SHADOW)
			{
				std::string mdV(md5((char*)fileName));
				generatedName = new char[mdV.length() + 1];
				memcpy(generatedName,mdV.c_str(),mdV.length() + 1);
			}
			if(mode == CVirtualFileSystem::DPMOD_SHADOW_WITH_EXTENSION)
			{
				std::string mdV(fileName);
				// separate the body and the extension to do not shadow the extension
				std::size_t idx = mdV.find(".");
				std::string body = mdV.substr(0, idx);
				std::string ext = mdV.substr(idx + 1, mdV.length());
				body = md5(body.c_str());
				//once separated and shadowed, append again
				body.append(".").append(ext);
				generatedName = new char[body.length() + 1];
				memcpy(generatedName,body.c_str(),body.length() + 1);
				
			}
			if(mode == CVirtualFileSystem::DPMOD_NORMAL)
			{
				generatedName = new char[strlen(fileName) + 1];
				memcpy(generatedName,fileName, strlen(fileName) + 1);
			}
			//then, write destination file
			PHYSFS_File* fileDest;

			fileDest = PHYSFS_openWrite(generatedName);
			PHYSFS_write(fileDest,buffer,1,fileSize);
			PHYSFS_close(fileDest);
			delete[] buffer;

			boost::filesystem::path ret_fileName(m_writingAllowedPath / boost::filesystem::path(generatedName));
			//std::string retFileName(ret_fileName.string().c_str());
			m_deployedManager.insert(fileName,ret_fileName.string().c_str());

			return ret_fileName.string();
			
		}


	};
};
